<?php

namespace org\copperforest\oak;
/**
 * Archivo que describe la clase Node
 * @author Alejandro Gama <alejandro.gama@gmail.com>
 * @version 1.0
 * @package oak
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 */

/**
 * La clase abstracta Node es la base del framework
 * Los objetos Node se obtiene a través de una política
 */
abstract class Node extends \org\copperforest\Template
{

    private static $sequence;

    /**
     * @access private
     */
    public static $_METHOD = null;

    /**
     * @access private
     */
    private static $_asynchronous = false;

    /**
     * Grupo de roles al que pertenece el usuario
     * @access private
     * @var int
     */
    private static $_defaultGroup;

    /**
     * @internal Indica si ya estamos preparados para el despliegue de las plantillas
     * @access public
     * @var boolean
     */
    static protected $_prepared = false;

    /**
     * Indica si se ha llamado al método forgot de algún nodo
     * @access private
     * @var boolean
     */
    //static protected $_forgotten = false;

    /**
     * Indica si el nodo root ha cambiado durante el procesamiento del evento
     * @access private
     * @var boolean
     */
    static private $_rootChanged = false;

    /**
     * Guardamos la traza a la que debemos volver en un método POST si no hay cambio de root
     * @access private
     * @var string
     */
    static private $_postRootTrace = '';

    /**
     * Nodo root del documento
     * @var Node
     */
    static protected $root = null;

    /**
     * Id del nodo root (usado antes de _prepare()) en base64
     * @access private
     * @var string
     */
    static private $_rootId;

    /**
     * Politica del nodo root (usada antes de _prepare())
     * @access private
     * @var string
     */
    static private $_rootPolicy;

    /**
     * Grupo del nodo root (usada antes de _prepare())
     * @access private
     * @var string
     */
    static private $_rootGroup;

    /**
     * Nodo root asíncrono: El que cancela el burbujeo del evento a través de la traza
     * @access private
     * @var Node
     */
    static private $_cancelNode;

    /**
     * Id del nodo root (usado antes de _prepare()) en base64
     * @access private
     * @var string
     */
    static private $_cancelNodeId;

    /**
     * Politica del nodo root (usada antes de _prepare())
     * @access private
     * @var string
     */
    static private $_cancelNodePolicy;

    /**
     * Grupo de roles del nodo root (usada antes de _prepare())
     * @access private
     * @var string
     */
    static private $_cancelNodeGroup;

    /**
     * Traza del nodo root asincrono
     * @access private
     * @var string
     */
    static private $_cancelNodeTrace = '';

    /**
     * Codigo básico para todos los eventos form
     * @access private
     * @var string
     */
    static private $_baseForm = '';

    /**
     * Array con los nodos por los que pasará el evento
     * @access private
     * @var array
     */
    static private $_traceNodes = array( );

    /**
     * Array de los nodos obtenidos durante el procesamiento del evento para que mantengan los valores en los métodos fill
     * @var array
     */
    private static $_cache = array( );

    /**
     * Array de los providers de cada politica
     * @var array
     */
    private static $_providers = array( );

    /**
     * Array de parametros extraidos del PATH_INFO
     * @var array
     */
    protected static $_friendlyParams = array( );
    protected static $_friendlyPointer = 0;
    //??porque protected? para accederlos desde métodos estáticos??
    /**
     * Traza del nodo
     * @access private
     * @var string
     */
    private $_trace;

    /**
     * Identificador de la politica con la que se obtubo el nodo
     * @access private
     * @var int
     */
    protected $_policyId;

    /**
     * Codificación del id del nodo
     * @access private
     * @var string
     */
    protected $_idB64;

    /**
     * GRupo de roles con los que se obtubo el nodo
     * @access private
     * @var string
     */
    protected $_group;

    /**
     * $node->_policyId .'~'.$node->_group. '~' . $node->_idB64;
     */
    protected $asyncId;
    //
    //ruta desde este nodo hasta la raiz del arbol
    /**
     * @access private
     * @var string
     */
    protected $_basePath = array( );

    /**
     * Identificador del objeto. No es único, puede haber objetos implementando distintas clases que tengan el mismo id.
     * @var mixed
     */
    protected $id;
    private $_cached_if_possible;
    static private $cache;

    /**
     * Identificador de la politica usada para obtener el objeto.
     * @var mixed
     */
    protected $policy;

    /**
     * Colección de errores durante el procesamiento del evento.
     * Establecer algún valor durante la ejecución del handlePOST evita que se ejecute el método doPOST
     * @var array
     */
    protected $errors = array( );

    /**
     * Número de hijos del objeto
     * @access private
     * @var int
     */
    private $numChilds = 0;

    /**
     * Nodo padre del actual o null en caso de que no tenga
     * @var Node
     */
    private $_parentNode = null;

    /**
     * Id del nodo padre del actual o null en caso de que no tenga
     * @var string|array
     */
    private $_parentNodeId;

    /**
     * Politica del nodo padre del actual o null en caso de que no tenga
     * @var int
     */
    private $_parentNodePolicy;

    /**
     * Grupo del nodo padre del actual o null en caso de que no tenga
     * @var int
     */
    private $_parentNodeGroup;

    /**
     * Primer hijo del nodo actual
     * @var Node
     */
    protected $firstChild = null;

    /**
     * Ultimo hijo del nodo actual
     * @var Node
     */
    protected $lastChild = null;

    /**
     * Siguiente hermano del nodo padre
     * @var Node
     */
    protected $nextSibling = null;

    /**
     * Anterior hermano del nodo padre
     * @var Node
     */
    protected $previousSibling = null;

    /**
     * Array de hijos del nodo actual
     * @var array
     */
    protected $childNodes = array( );
    protected $_friendyPath = array( );

    /**
     * Agrega un hijo al nodo actual
     * @param Node $child
     * @return boolean
     */
    final function appendChild( $child )
    {
        if ( !self::$_prepared )
                throw new Exception( "No se pueden agregar hijos dentro del procesamiento del evento. Use el método fill para este propósito" );

        $child->_parentNode = $this;

        if ( $this->numChilds == 0 ) $this->firstChild = $child;
        else {
            $this->childNodes[ $this->numChilds - 1 ]->nextSibling = $child;
            $child->previousSibling = $this->childNodes[ $this->numChilds - 1 ];
        }

        $this->childNodes[ ] = $child;
        $this->lastChild = $child;

        $this->numChilds++;

        return true;
    }

    /**
     * Elimina cualquier llamada a métodos guardadas en sesión a través del operador '_'
     * @return Node
     */
    final protected function forget()
    {
        if ( self::$_prepared )
                throw new Exception( "No se pueden hacer forget() fuera del procesamiento del evento" ); //si no tendríamos que rehacer en nodo e insertarlo en el arbol



            
//self::$_forgotten=true;

        $_SESSION[ 'Node::_methods' ][ $this->_policyId ][ $this->_idB64 ] = array(
                );
        unset( self::$_cache[ $this->_nodeId ][ $this->_group ][ $this->_idB64 ] ); //Esto no es necesario ya que se va a propiciar un relocation
        //¿Porque necesitaba un relocation?
        return $this;
    }

    /**
     * Método que se ejecutará cuando el nodo actual está dentro de la traza de un evento POST (y no haya sido cancelado el evento por un nodo anterior en la traza). Deberemos reescribir el las clases Node que definamos.
     * @param Event $e
     */
    function handlePOST( Event $e )
    {
        
    }

    /**
     * Método que se ejecutará cuando el nodo actual está en la traza de un evento POST (y no haya sido cancelado el evento por un nodo anterior en la traza), se ejecutará después de handlePOST y siempre que el array $errors esté vacío. Deberemos reescribir el las clases Node que definamos.
     * @param Event $e
     */
    function doPOST( Event $e )
    {
        
    }

    /**
     * Método que se ejecutará cuando el nodo actual está en la traza de un evento GET (y no haya sido cancelado el evento por un nodo anterior en la traza).
     * @param Event $e
     */
    function handleGET( Event $e )
    {
        if ( $e->tag == 'head' ) {
            self::$_stage = 'head';
            $e->cancelBubble();
        } else if ( $e->tag == 'body' ) {
            self::$_stage = 'body';
            $e->cancelBubble();
        }
    }

    /**
     * Método que se ejecutará después del evento en todos los nodos a partir de root. Debe reescribirse en la clase que herede de Node para evaluar los datos que ha producido el evento y añadir los hijos que sean necesarios.
     */
    function fill()
    {
        
    }

    /**
     * Método que establece el array $errors. Si se llama desde el método handlePOST evitará que se ejecute el método doPOST.
     * Si queremos que estos errores permanezcan durante la sesión lo llamaremos como <code>$this->_setErrors($errores);</code>
     * @param array $errors
     */
    /*
      final function setErrors($errors) {
      $this->errors = $errors;
      }
     *
     */

    /**
     * Devuelve el id del objeto
     * @return mixed
     */
    final function getId()
    {
        return $this->id;
    }

    final function getParentNode()
    {
        if ( $this->_parentNode == null && !empty( $this->_parentNodeId ) )
                $this->_parentNode = Node::_getInstance( $this->_parentNodePolicy,
                            $this->_parentNodeId, $this->_parentNodeGroup );

        return $this->_parentNode;
    }

    final static function isAsynchronous()
    {
        return self::$_asynchronous;
    }

    final static function setAsynchronous( $async )
    {
        self::$_asynchronous = $async;
        $_SESSION[ 'Node::_asynchronous' ] = $async;
    }

    final protected function nextPathParam()
    {
        return next( self::$_friendlyParams );
    }

    final protected function prevPathParam()
    {
        return prev( self::$_friendlyParams );
    }

    final protected function pushPathParam( $param )
    {
        if ( !self::$_prepared )
                throw new Exception( "No se deben establecer parametros durante el procesamiento del evento. Use el método fill para este propósito" );

        $this->_friendlyPath[ ] = $param;
    }

    final protected function friendly( $stack )
    {
        $p = null;
        $path = array( );

        while ( ( $p = $this->getParentNode() ) != null )
            $path = array_merge( $path, $p->_friendlyPath );

        $path = array_merge( $path, $stack );

        //hodgepodge
        //hotchpotch
        //0
        //hp
        //h
        return CO_PROJECO_INDEX . (REWRITE_ENGINE ? '0' : '0.php/') . implode( '/',
                        $path );
    }

    final protected function asyncFriendly( $stack )
    {
        $p = null;
        $path = array( );

        while ( ( $p = $this->getParentNode() ) != null )
            $path = array_merge( $path, $p->_friendlyPath );

        $path = array_merge( $path, $stack );

        if ( self::$_asynchronous )
                return CO_PROJECO_INDEX . (REWRITE_ENGINE ? '0' : '0.php/') . implode( '/',
                            $path ) . '" onclick="if( document.getElementById(\'_' . $this->asyncId . '\') && ajax_change_state ){ var request=new XMLHttpRequest( \'GET\', \'' . CO_PROJECO_INDEX . (REWRITE_ENGINE ? '0' : '0.php/') . implode( '/',
                            $stack ) . '?_trc=' . $this->_trace . '&_async=1\', true, null, null ); request.onreadystatechange=ajax_on_change_state; request.onreadystatedone=ajax_replace_node; request.onreadystateloading=ajax_transition; request._loading_node_id=\'_' . $this->asyncId . '\'; request.send(); return false }';
        else
                return CO_PROJECO_INDEX . (REWRITE_ENGINE ? '0' : '0.php/') . implode( '/',
                            $path );
    }

    protected function handleFriendly()
    {
        
    }

    /**
     * Método a ejecutar en las plantillas para generar un evento al enviar el formulario dónde se ejecture
     * @param string $tag Etiqueta del evento
     * @return string
     */
    final protected function formEvent( $tag )
    {
        return
                self::$_baseForm .
                '<input type="hidden" name="_trc" value="' . $this->_trace . '" /><input type="hidden" name="_evt" value="' . htmlspecialchars( $tag ) . '" />';
    }

    /**
     * Método a ejecutar en las plantillas cuando queremos enviar un evento a través de un enlace, debe situarse en el href del elemento anchor <a>
     * @param string $tag Etiqueta del evento
     * @param array $params Array de parametros adicionales que serán enviados en la url
     * @return string Devuelve un string con la url que genera el evento
     */
    final public function urlEvent( $tag = null, $params = null )
    {
        return '?_trc=' . $this->_trace . (!empty( $tag ) ? '&_evt=' . urlencode( $tag ) : '') . (!empty( $params ) ? '&' . http_build_query( $params ) : '');
    }

    /**
     * Metódo para ejecutar en las plantillas cuando queramos enviar un evento asíncrono a través de un formulario. El evento asincrono actualizará automaticamente todos los nodos que haya en la traza hasta que uno de ellos cancele el burbujeo
     * Para que esto se consiga la actualización el bloque de código a actualizar debe ser marcado con el atributo <code>id="<?=$this->getAsyncId()?>"</code>
     * @param string $tag Etiqueta
     * @return string Devuelve un string con el código HTML a insertar en el formulario
     */
    final protected function asyncFormEvent( $tag )
    {
        if ( self::$_asynchronous )
                return self::$_baseForm .
                    '<input type="hidden" name="_trc" value="' . $this->_trace . '" /><input type="hidden" name="_evt" value="' . htmlspecialchars( $tag ) . '" /><input type="hidden" name="_asyncId" value="_' . $this->asyncId . '" />';
        else
                return self::$_baseForm .
                    '<input type="hidden" name="_trc" value="' . $this->_trace . '" /><input type="hidden" name="_evt" value="' . htmlspecialchars( $tag ) . '" />';
    }

    /**
     * Método a ejecutar en las plantillas para enviar un evento asíncrono a través de un enlace. Como el otro método asincrono tratará de actualizar hasta que se cancele el evento en uno de los nodos
     * @param string $tag Etiqueta del evento
     * @param array Parametros adicionales que queramos enviar con elevento
     * @return string Cadena de caracteres con el trozo de código HTML a introducir dentro del href
     */
    final protected function asyncUrlEvent( $tag = null, $params = null )
    {
        $url = '?_trc=' . $this->_trace . (!empty( $tag ) ? '&_evt=' . urlencode( $tag ) : '') . (!empty( $params ) ? '&' . http_build_query( $params ) : '');

        if ( self::$_asynchronous )
                return $url . '" onclick="if( document.getElementById(\'_' . $this->asyncId . '\') && ajax_change_state ){ var request=new XMLHttpRequest( \'GET\', \'' . $url . '&_async=1\', true, null, null ); request.onreadystatechange=ajax_on_change_state; request.onreadystatedone=ajax_replace_node; request.onreadystateloading=ajax_transition; request._loading_node_id=\'_' . $this->asyncId . '\'; request.send(); return false }';
        else return $url;
    }

    /**
     * REALMENTE ESTE MÉTODO YA NO ES NECESARIO AL ESTAR REESCRITO EL OBJETO XMLHTTPREQUEST
     * Método para generar eventos AJAX en las plantillas
     * @param string $method "POST" o "GET"
     * @param string $tag etiqueta del evento
     * @param string $jscallback función JavaScript que llamar cuando se vuelva del evento
     * @return string Código JavaScript con la llamada al evento
     */
    /*
      final protected function XMLHttpEvent($method, $tag, $jscallback) {
      return 'var request=new XMLHttpRequest( \''.$method.'\', \'?_trc=' . $this->_trace . '&_evt=' . urlencode($tag) . '\', true, null, null ); request.onreadystatechange='.$jscallback.'; request.send();';
      }
     */

    /**
     * Id a establecer en las plantillas en el bloque HTML más externo. Si se hacen eventos asincronos se consigue actualizar sólo los elementos necesarios.
     * @return strint id del elemento
     */
    final function getAsyncId()
    {
        return '_' . $this->asyncId;
    }

    /*
      static final function useNodeTemplate($stage,$content_type=null){
      self::$_stage=$stage;

      if(empty($content_type)){
      if(isset(self::$MIME_TYPE[$stage]))
      header('Content-Type: '.self::$MIME_TYPE[$stage]);
      else
      header('Content-Type: application/octet-stream');
      }
      else
      header('Content-Type: '.$content_type);

      }
     */

    /**
     * Metodo que procesa el evento a través del array _traceNodes
     * @access private
     * @param Event $e
     */
    static final function _eventProcess( Event $e )
    {
        //self::$_friendlyParams=array(); //SI HAY EVENTO SE ELIMINAR LOS PARAMETROS PASADOS POR PATH

        $oldRootId = self::$_rootId; //AQUI NO ESTA ESTABLECIDO!!!! aQUI SOLO SE EL IDENTIFICADOR Y EL
        $oldRootPolicy = self::$_rootPolicy;
        $oldRootGroup = self::$_rootGroup;
        //tengo que seguir la traza desde el nodo más alto al más bajo y pasando el evento por ellos
        //TEGNO QUE GUARDAR EN $_cancelNode EL NODO MÃ�S ELEVEDADO DE LA TRAZA QUE RECIBIMOS
//obtenemos de cache el nodo que produjo el evento
        //SI NO HAY EVENTO NO NECESITO SABER EL $CancelNode????

        if ( $e->tag !== null ) {

            foreach ( self::$_traceNodes as $n ) {

                if ( $e->bubble ) {

                    list($cid, $grupo, $idB64) = explode( '~', $n ); //pero los ids pueden tener guiones
                    $priv = self::_getInstance( $cid, $idB64, $grupo ); //aqui no parece haber problema
                    //
                    //@TODO Si el método es POST debería eliminar las caches de los nodos de la traza!!


                    if ( self::$_cancelNodeId == null ) {

                        //self::$_cancelNode = $priv;
                        self::$_cancelNodeId = $priv->_idB64;
                        self::$_cancelNodePolicy = $priv->_policyId;
                        self::$_cancelNodeGroup = $priv->_group;
                        self::$_cancelNodeTrace = $priv->asyncId;

                        $e->setSource( $n->_policyId, $n->_group, $n->_idB64 );
                    }

                    if ( self::$_METHOD == self::POST ) {

                        $n->handlePOST( $e );

                        if ( empty( $n->errors ) ) $n->doPOST( $e );
                        else $n->bubble = false; //ya hago aqui el cancelBubble
                    }
                    else $n->handleGET( $e );

                    //stage ajax...
                    if ( self::$_stage !== 'head' ) { //POR CADA NODO QUE PASA EL EVENTO EL ES ROOT, HASTA QUE ALGUIEN CANCELE EL BUBBLE
                        //self::$_cancelNode = $priv;
                        self::$_cancelNodeId = $priv->_idB64;
                        self::$_cancelNodePolicy = $priv->_policyId;
                        self::$_cancelNodeGroup = $priv->_group;
                        self::$_cancelNodeTrace = $priv->asyncId;
                    }
                }
                else
                        self::$_cancelNodeTrace = self::$_cancelNodeTrace . '/' . $priv->asyncId;
            }
        }

        if ( self::$root != null ) {
            self::$_rootChanged = ( $oldRootId != self::$root->_idB64 || $oldRootPolicy != self::$root->_policyId || $oldRootGroup != self::$root->_group );

            self::$_rootId = self::$root->_idB64;
            self::$_rootPolicy = self::$root->_policyId;
            self::$_rootGroup = self::$root->_group;
        }

        //puede haber cambiado por el evento
        self::$_defaultGroup = User::getLogged()->_getGroup();
    }

    /**
     * Establece ciertos valores previos para que el despliegue de las plantillas sea más rápido cuando es una llamada asincrona
     * @access private
     */
    final public static function _prepareCancelNode()
    {
        self::$_friendlyParams = explode( '/', $_SERVER[ 'PATH_INFO' ] );

        self::$_prepared = true;

        if ( self::$_rootChanged ) {
            //entonces el nodo a substituir es el Root
            self::$_cancelNode = self::_getInstance( self::$_rootPolicy,
                            self::$_rootId, self::$_rootGroup );
        } else if ( empty( self::$_cancelNodeId ) ) { //pero si está establecido en el evento //si he hecho un clear de root...?
            //Y si no hay traza?????? no debería pasar nunca....SI, cuando manejamos friendly
            list( $cid, $grupo, $idB64 ) = explode( '~', self::$_traceNodes[ 0 ] );
            self::$_cancelNode = self::_getInstance( $cid, $idB64, $grupo );
        }
        else
                self::$_cancelNode = self::_getInstance( self::$_cancelNodePolicy,
                            self::$_cancelNodeId, self::$_cancelNodeGroup );



        self::$_traceNodes = null;
        self::$_postRootTrace = null;


        self::$_baseForm = '<input type="hidden" name="_tkn" value="' . User::_getToken() . '" />';


        self::$_cancelNode->_trace = self::$_cancelNodeTrace;
        self::$_cancelNodeTrace = null;

        self::$_cancelNode->childNodes = array( );
        self::$_cancelNode->fill();

        foreach ( self::$_cancelNode->childNodes as $c ) $c->_prepareTrace();
    }

    /**
     * Establece ciertos parametros para que el despliegue de las plantillas se realize más rápido
     * @access private
     */
    final public static function _prepare()
    {

        self::$_friendlyParams = explode( '/', $_SERVER[ 'PATH_INFO' ] );

        //dependiendo si trajo algo la traza tendría que tomar el
        self::$_traceNodes = null;
        self::$_cancelNodeTrace = null;
        self::$_postRootTrace = null;

        self::$_prepared = true;

        self::$root = self::_getInstance( self::$_rootPolicy, self::$_rootId,
                        self::$_rootGroup );
        self::$root->_prepareTrace();


        self::$_baseForm = '<input type="hidden" name="_tkn" value="' . User::_getToken() . '" />';
    }

    /**
     * Establece la traza del objeto dentro del documento
     * @access private
     */
    final protected function _prepareTrace()
    {
        $this->_trace = $this->asyncId . ($this->_parentNode != null ? '/' . $this->_parentNode->_trace : '');

        $this->childNodes = array( );

        if ( $this->_cached_if_posible ) {

            $id = array( $this->_context, $this->policy, $this->_idB64 );

            $res = self::$cache->get( $id, $this->classname );

            if ( !$res ) $this->_cached_version = $res;
            else {

                $this->fill();
                foreach ( $this->childNodes as $c ) $c->_prepareTrace();
            }
        } else {
            $this->fill();
            foreach ( $this->childNodes as $c ) $c->_prepareTrace();
        }
    }

    function __toString()
    {
        if ( $this->_cached_version[ cf\Template::$_selector ] == null )
                echo $this->_cached_version[ cf\Template::$_selector ];

        else if ( $this->_cached_if_posible ) {

            $previously = ob_get_clean();
            ob_start();
            parent::__toString();
            $current = ob_get_clean();
            $this->_cached_version[ cf\Template::$_selector ] = $current;
            ob_start();

            echo $previously . $current;
        }
        else parent::__toString();
    }

    /**
     * Descompone la traza para su posterior procesamiento en _processEvent
     * @access private
     * @param string $trace paramétro $_GET['_trc']
     */
    final public static function _unserialize( $trace )
    {
        //Puedo recuperar la traza solo con apuntadores hacia atrás ->_parentNode
        //aqui ya voy a saber si tengo que enviar el CancelNode o el root
        //excepto cuando se trata de otro mime que va el CancelNode... es decir

        self::$_defaultGroup = User::getLogged()->_getGroup();


        if ( !isset( $_SESSION[ 'Node::_asynchronous' ] ) )
                $_SESSION[ 'Node::_asynchronous' ] = ASYNCHRONOUS;

        self::$_asynchronous = $_SESSION[ 'Node::_asynchronous' ];

        if ( isset( $_REQUEST[ '_async' ] ) && self::$_asynchronous ) //a pesar de que en sesion esté asincrono deberemos comprobar que se envia por JavaScript
                self::$_stage = 'ajax';

        if ( !isset( $_SESSION[ 'Node::_methods' ] ) )
                $_SESSION[ 'Node::_methods' ] = array( );


        if ( empty( $trace ) ) {

            self::$_rootId = self::_transID( 0 );
            self::$_rootGroup = 0;


            if ( isset( Deployment::$POLICIES_IDS[ 'index' ] ) )
                    self::$_rootPolicy = Deployment::$POLICIES_IDS[ 'index' ];
            else self::$_rootPolicy = Deployment::$POLICIES_IDS[ 'Index' ];

            self::$_traceNodes[ ] = self::$_rootPolicy . '~' . self::$_rootGroup . '~' . self::$_rootId; //Y el grupo con el que se selecciona
            //self::$_traceNodes[]=self::_getInstance(self::$_rootPolicy, self::$_rootId, self::$_rootGroup);
            self::$_postRootTrace = self::$_rootPolicy . '~' . self::$_rootGroup . '~' . self::$_rootId;
        }
        else {

            if ( strpos( $trace, '/' ) !== false ) {

                $nodos = explode( '/', $trace );

                $i = 0;
                foreach ( $nodos as $n ) {
                    self::$_postRootTrace = $n; //la traza del root es la ultima
                    //$n=explode('~',$n);
                    //self::$_traceNodes[]=self::_getInstance($n[0], $n[2], $n[1]);
                    self::$_traceNodes[ ] = $n;

                    if ( $i > 0 ) {
                        $n = explode( '~', $n );
                        self::$_traceNodes[ $i - 1 ]->_panrentNodeId = $n[ 2 ];
                        self::$_traceNodes[ $i - 1 ]->_panrentNodePolicy = $n[ 0 ];
                        self::$_traceNodes[ $i - 1 ]->_panrentNodeGroup = $n[ 1 ];
                    }

                    $i++;
                }
            } else {
                //$n=explode('~',$trace);
                //self::$_traceNodes[]=self::_getInstance($n[0], $n[2], $n[1]);

                self::$_traceNodes[ ] = $trace;
                self::$_postRootTrace = $trace;
            }

            self::$_cancelNodeTrace = $trace; //por si no hay evento.. si no hay evento ... debería recuperar solo el primer nodo????
            list(self::$_rootPolicy, self::$_rootGroup, self::$_rootId) = explode( '~',
                    self::$_postRootTrace ); //pero los ids pueden tener guiones
        }
    }

    /**
     * Devuelve la traza del nodo root
     * @access private
     * @return string la traza del nodo root
     */
    final static function _getRootTrace()
    {
        if ( in_array( self::$_stage,
                        array( 'meta', 'head', 'js', 'css', 'body' ) ) ) {

            if ( !self::$_rootChanged ) return self::$_postRootTrace;
            else
                    return self::$root->asyncId; //el root se obtiene con el grupo base
        }
        else //si lo llamamos desde ajax entonces buscamos una actualizacion del CancelNode
                return self::$_cancelNodeTrace . '&_async=1';
    }

    /**
     * Devuelve el nodo root
     * @access private
     * @return Node El nodo root
     */
    final static function _getRoot()
    {
        return self::$root;
    }

    /**
     * Devuelve el nodo que cancela el burbujeo del evento
     * @return Node Devuelve el nodo root asíncrono
     */
    final static function _getCancelNode()
    {
        return self::$_cancelNode;
    }

    /**
     * Método que establece el valor del id cuando la politica lo define como un array al ser extraido de la Base de Datos.
     * @access private
     * @param string $name
     * @param mixed $value
     */
    public function __set( $name, $value )
    {
        if ( substr( $name, 0, 3 ) === 'id[' && substr( $name, -1, 1 ) === ']' ) {
            if ( !is_array( $this->id ) ) $this->id = array( );

            $this->id[ ( int ) substr( $name, 3, -1 ) ] = $value;
        }
    }

    /**
     * Método que da covertura al operador '_' de llamada a los métodos del objeto
     * Al ejecutar un método con este operador delante se guarda el valor en sesión
     * Relanzando la llamada cada vez que se presente este objeto
     * @access private
     * @param strig $m
     * @param array $params
     * @return mixed
     */
    function __call( $m, $params )
    {//deberÃ­a pasarlos por referencia ya que la funcion puede tenerlos definidos por referencia
        //primero tengo que ver en que parte estoy
        if ( $params === null ) $params = array( );

        if ( strpos( $m, '_' ) === 0 ) { //llamada a método persistente
            $method = substr( $m, 1 );

            if ( !isset( $_SESSION[ 'Node::_methods' ][ $this->_policyId ] ) )
                    $_SESSION[ 'Node::_methods' ][ $this->_policyId ] = array( );

            if ( !isset( $_SESSION[ 'Node::_methods' ][ $this->_policyId ][ $this->_idB64 ] ) )
                    $_SESSION[ 'Node::_methods' ][ $this->_policyId ][ $this->_idB64 ] = array(
                        );

            $_SESSION[ 'Node::_methods' ][ $this->_policyId ][ $this->_idB64 ][ $method ] = $params;


            return call_user_func_array( array( $this, $method ), $params );
        }
    }

    /**
     * Metodo para obtener Nodos.
     * @param string $policy Clase o Politica a seguir para la obtención del nodo
     * @param mixed $id Id del Nodo
     * @return Node el nodo requerido o null si no existe o no tenemos privilegios sobre él.
     */
    final static function getInstance( $id, $policy,
            SecurityContext $context = null )
    {
        $idB64 = self::_transID( $id );

        if ( !empty( $context ) ) {
            $group = SecurityContext::getContextId( $context );
        }
        else $group = self::$_defaultGroup;

        $policyId = Deployment::$POLICIES_IDS[ $policy ];

        self::_getInstance( $policyId, $idB64, $group );
    }

    //deberíamos tener asegurado que el contexto es posible???????????????
    final static function _getInstance( $policy, $id = null, $context = null,
            $cached = false )
    {




        $idB64 = $id;
        $id = self::_untransID( $id ); //esto tiene que devolver id , politica y grupo

        $policyId = $policy;
        $policy = Deployment::$POLICIES_NAMES[ $policy ];


        if ( !isset( self::$_providers[ $policyId ] ) )
                self::$_providers[ $policyId ] = new Deployment::$POLICY_PROVIDER[ $policyId ]( $policyId );


        if ( !empty( $context ) && self::$_providers[ $policyId ]->isContextIdAvailable( $context ) )
                $group = $context;
        else $group = self::$_defaultGroup;






        if ( isset( self::$_cache[ $policyId ] ) && isset( self::$_cache[ $policyId ][ $group ] ) && isset( self::$_cache[ $policyId ][ $group ][ $idB64 ] ) )
                return self::$_cache[ $policyId ][ $group ][ $idB64 ];



        $node = self::$_providers[ $policyId ]->getInstance( $group, $id );



        $node->_idB64 = $idB64;
        $node->_policyId = $policyId;
        $node->_group = (empty( $rolesOrGroup ) ? 0 : $group);

        $node->asyncId = $node->_policyId . '~' . $node->_group . '~' . $node->_idB64;

        $node->policy = $policy;


        if ( !isset( self::$_cache[ $policyId ] ) )
                self::$_cache[ $policyId ] = array( );

        if ( !isset( self::$_cache[ $policyId ][ $group ] ) )
                self::$_cache[ $policyId ][ $group ] = array( );

        self::$_cache[ $policyId ][ $group ][ $idB64 ] = $node;

        //Y llamo a las funciones
        if ( isset( $_SESSION[ 'Node::_methods' ][ $policyId ] ) && isset( $_SESSION[ 'Node::_methods' ][ $policyId ][ $idB64 ] ) )
                foreach ( $_SESSION[ 'Node::_methods' ][ $policyId ][ $idB64 ] as
                        $method => $params )
                    call_user_func_array( array( $node, $method ), $params );

        if ( !empty( self::$_friendlyParams ) )//puedo comprobar si durante la ejecucion se ha llamado a algun metodo y ver si es distinto de lo que tenía
                $node->handleFriendly();

        // var_dump($node);
        return $node;
    }

    /**
     * Obtiene una colección de nodos de una determinada política de seguridad.  Devuelve el número total de nodos (sin clausula LIMIT) y los elementos serán devueltos en el array $datos
     * @param string $policy Clase o Política de seguridad requerida
     * @param array $datos Array vacío donde se almacenarán los nodos obtenidos
     * @param string $sql WHERE a mayores para incluir en la SQL
     * @param array $orders Array de campos por los que se puede ordenar la lista
     * @param int $num Numero de elementos a mostrar
     * @param int $offset Desplazamiento a partir del registro inicial que se van a obtener
     * @param int $total Establecemos $total al valor obtenido como resultado de este método en anteiores ocasiones para que no vuelva a calcularlo
     * @return int
     */
    final static function getArray( $policy, &$datos, $context = null,
            $cached = false, $orders = null, $num = null, $offset = null,
            $total = null, $sql = null )
    {
        $numtotal = 0;
        $policyId = Deployment::$POLICIES_IDS[ $policy ];
        $isResource = (Deployment::$POLICY_TYPE[ $policyId ] == self::ISRESOURCE);
        $isClass = (Deployment::$POLICY_TYPE[ $policyId ] == self::ISCLASS);


        //en lugar de self::$_defaultGroup tendría que tener otro group

        if ( !empty( $roles ) ) $group = User::_getCurrentGroup( $roles );
        else $group = self::$_defaultGroup;

        if ( $isResource ) {
            list($sqlini, $idField, $idNumeric, $indiceClase, $end) = SecurityCache::getResourceInfo( $group,
                            $policyId );

            if ( !empty( $sql ) ) $sql = $sqlini . ' AND (' . $sql . ')';
            else $sql = $sqlini;
        }
        else if ( !$isClass ) return null;


        if ( !empty( $end ) ) $sql.=' ' . $end;

        $directions = array( -1 => 'DESC', 1 => 'ASC' );
        if ( !empty( $orders ) ) {
            $sql.=' ORDER BY ';

            if ( !is_array( $orders ) ) $sql.=$orders;

            else {

                $comma = false;
                foreach ( $orders as $field => $direction ) {
                    if ( $direction != 0 ) {
                        if ( $comma ) $sql.=', ';
                        else $comma = true;

                        $sql.=$field . ' ' . $directions[ $direction ];
                    }
                }
            }
        }


        //http://www.sqlservercentral.com/articles/paging/69892/
        /*
          -- Key-Seek algorithm
          WITH
          Keys
          AS      (
          -- Step 1 : Number the rows from the non-clustered index
          -- Maximum number of rows = @PageNumber * @PageSize
          SELECT  TOP (@PageNumber * @PageSize)
          rn = ROW_NUMBER() OVER (ORDER BY P1.post_id ASC),
          P1.post_id
          FROM    dbo.Post P1
          ORDER   BY
          P1.post_id ASC
          ),

          SelectedKeys
          AS      (
          -- Step 2 : Get the primary keys for the rows on the page we want
          -- Maximum number of rows from this stage = @PageSize
          SELECT  TOP (@PageSize)
          SK.rn,
          SK.post_id
          FROM    Keys SK
          WHERE   SK.rn > ((@PageNumber - 1) * @PageSize)
          ORDER   BY
          SK.post_id ASC
          )



          SELECT  -- Step 3 : Retrieve the off-index data
          -- We will only have @PageSize rows by this stage
          SK.rn,
          P2.post_id,
          P2.thread_id,
          P2.member_id,
          P2.create_dt,
          P2.title,
          P2.body
          FROM    SelectedKeys SK
          JOIN    dbo.Post P2
          ON  P2.post_id = SK.post_id
          ORDER   BY
          SK.post_id ASC;
         */

        list( $select, $select_update, $from, $from_update, $where, $idNumeric, $end ) = SecurityCache::getResourceInfo( self::$_group,
                        $nodeId );

        if ( !empty( $sql_where ) ) $where.=' AND (' . $sql_where . ')';

        if ( !empty( $end ) ) $where.=' ' . $end;



        if ( !empty( $offset ) ) { //tiene que estar num tb?..no
            if ( Config::$helper->isLIMITAvailable() ) {
                $sql = $select . $sql_from;

                if ( !empty( $order ) ) $sql.=' ORDER BY ' . $order;

                if ( !empty( $num ) ) $sql.=' LIMIT ' . $offset . ',' . $num;
            }
            else if ( !empty( $num ) ) {
                //http://www.mssqltips.com/sqlservertip/2696/comparing-performance-for-different-sql-server-paging-methods/
                //segun el blog anterior las optimizaciones de sql 2012 solo afectan a la primera pagina, o lo mismo que si fuera offset a 0 y TOP
                $sql = 'SELECT TOP(' . $num . ') * FROM (SELECT ' . $select . ', ROW_NUMBER() OVER (ORDER BY ' . $order . ') AS [R0wNumb3r] ' . $sql_from . $sql_where . ') AS [P4g3dT4bl3] WHERE [R0wNumb3r] >' . $offset;
            }
        } else if ( !empty( $num ) ) {

            if ( Config::$helper->isLIMITAvailable() )
                    $sql = 'SELECT ' . $select . $sql_from . $sql_where . ( empty( $order ) ? '' : ' ORDER BY ' . $order) . ' LIMIT ' . $num;
            else
                    $sql = 'SELECT TOP(' . $num . ') ' . $select . $sql_from . $sql_where . ( empty( $order ) ? '' : ' ORDER BY ' . $order);
        }
        else
                $sql = 'SELECT ' . $select . $sql_from . $sql_where . ( empty( $order ) ? '' : ' ORDER BY ' . $order);




        //SI ES UN REQUEST POST PORNGO UN FOR UPDATE AL FINAL DEL SELECT
        $calculeRows = empty( $total ) || ($total - 2 * $num) < $offset;

        //   if ($calculeRows)
        //       $sql = 'SQL_CALC_FOUND_ROWS ' . $sql;
        //   $sql = 'SELECT SQL_SMALL_RESULT ' . $sql;
//echo $sql;
        //if (self::$_METHOD == self::POST && !self::$_prepared)
        //    $sql.=' FOR UPDATE'; //solo lo que sea antes de la ejecución del método __prepare
        // $coincidencias=array();
        // if(preg_match('/\/SERIALNUMBER=(([0-9]{8}[A-Z]{1})|([LMXYZ]{1}[0-9]{7}[A-Z]{1}))/i',$_SERVER['SSL_CLIENT_S_DN'],$coincidencias) && $coincidencias[1]==='99999999R') //13131473Z
        //        echo $sql;
        ///ANTES DEL get-objects tengo que hacer un fetch row

        if ( $calculeRows ) {
            //$numtotal = Config::$default->get_rows('SELECT FOUND_ROWS()', true)->getField(0);
            $numtotal = Conifg::$default->get_rows( 'SELECT COUNT(*) FROM ' . $from . ' WHERE ' . $sql_where )->getField( 0 );
        }
        else $numtotal = $total;

        $resultado = Config::$default->get_objects( $sql );
        if ( $resultado->numRows() === 0 ) return 0;




        //no queremos llegar a la ultima página y que no exista o que existan más
        if ( $calculeRows )
                $numtotal = MyLink::$default->get_rows( 'SELECT FOUND_ROWS()',
                            true )->getField( 0 );
        else $numtotal = $total;


        if ( !isset( self::$_cache[ $policyId ] ) )
                self::$_cache[ $policyId ] = array( );

        if ( !isset( self::$_cache[ $policyId ][ $group ] ) )
                self::$_cache[ $policyId ][ $group ] = array( );

        $nodegroup = (empty( $roles ) ? 0 : $group);


        foreach ( $resultado as $r ) { //aqui se está haciendo implicitamente el fetch
            $idB64 = self::_transID( $r->id );

            $r->_policyId = $policyId;
            $r->_idB64 = $idB64;
            $r->_group = $nodegroup;

            $r->asyncId = $r->_policyId . '~' . $r->_group . '~' . $r->_idB64;

            $r->policy = $policy;


            if ( isset( self::$_cache[ $policyId ] ) && isset( self::$_cache[ $policyId ][ $group ] ) && isset( self::$_cache[ $policyId ][ $group ][ $idB64 ] ) ) {
                $datos[ ] = self::$_cache[ $policyId ][ $group ][ $idB64 ];
            } else {
                self::$_cache[ $policyId ][ $group ][ $idB64 ] = $r;

                if ( isset( $_SESSION[ 'Node::_methods' ][ $policyId ] ) && isset( $_SESSION[ 'Node::_methods' ][ $policyId ][ $idB64 ] ) )
                        foreach ( $_SESSION[ 'Node::_methods' ][ $policyId ][ $idB64 ] as
                                $method => $params )
                            call_user_func_array( array( $r, $method ), $params );


                if ( !empty( self::$_friendlyParams ) ) $r->handleFriendly();

                $datos[ ] = $r;
            }
        }


        $resultado->free();
        return $numtotal;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Transforma una cadena o un array de cadenas en su codificacion b64url, separando cada posicion del array por un punto (.)
     * @access private
     * @param mixed $id Un id o un array de ids a trasformar
     * @return string Devuelve el id codificado
     */
    public final static function _transID( $id )
    {
        if ( is_array( $id ) ) {
            ksort( $id ); //de que forma no vendrían ordenados los ids?

            foreach ( $id as &$i )
                    $i = strtr( base64_encode( $i ),
                        array( '+' => '-', '/' => '_', '=' => '' ) );

            return implode( '.', $id );
        }

        return strtr( base64_encode( $id ),
                array( '+' => '-', '/' => '_', '=' => '' ) );
    }

    /**
     * Transforma una cadena codificada en b64urlo en una cadena o array de cadenas
     * @access private
     * @param string $id
     * @return mixed
     */
    public final static function _untransID( $id )
    {


        if ( strpos( $id, '.' ) !== false ) {
            $id = explode( '.', $id );
            foreach ( $id as &$n )
                    $n = base64_decode( strtr( $n,
                                array( '-' => '+', '_' => '/' ) ) );
            return $id;
        }

        return base64_decode( strtr( $id, array( '-' => '+', '_' => '/' ) ) );
    }

    static function b64url_encode( $str )
    {
        return strtr( base64_encode( $str ),
                array( '+' => '-', '/' => '_', '=' => '' ) );
    }

    static function b64url_decode( $str )
    {
        return base64_decode( strtr( $str, array( '-' => '+', '_' => '/' ) ) );
    }

    static function _init()
    {

        if ( !isset( $_REQUEST[ '_srt' ] ) || !isset( $clases_secuencias[ $_REQUEST[ '_str' ] ] ) )
                self::$sequence = current( $classes_secuencias );
        else self::$sequence = $clases_secuencias[ $_REQUEST[ '_str' ] ];
    }

    static function handleRequest()
    {

        if ( get_magic_quotes_gpc() ) {

            $_POST = array_map( 'stripslashes_deep', $_POST );
            $_GET = array_map( 'stripslashes_deep', $_GET );
            $_COOKIE = array_map( 'stripslashes_deep', $_COOKIE );
            $_REQUEST = array_map( 'stripslashes_deep', $_REQUEST );
        }

        CT::startSession();

        $subordinates = $_SESSION[ 'USER' ]->getSubordinates();

        $cuenta = 0;
        foreach ( $subordinates as $s )
                if ( in_array( $s, $trace_roles ) ) $cuenta++;

        if ( $cuenta < count( $subordinates ) ) {
            
        }

        //ahora debería crear una secuencia de plantilla
        //Proceso el evento!!!! no tiene porque llevar tag o si?
        //tengo que encontrar una forma de leer parametros sin eventos, o estabecer raiz sin eventos
        //bueno puedo
        CT::writeSession();

        restore_error_handler();
        //ANTES TENGO QUE MANDAR LAS CABECERAS!!!!!!!!!!


        while ( !self::$sequence->process( $node ) );
    }

    static function _finish()
    {
//        foreach(self::$cached)
    }

}

Node::_init();
register_shutdown_function( array( 'Node', '_finish()' ) );

$_SERVER[ 'REQUEST_METHOD' ] = strtoupper( $_SERVER[ 'REQUEST_METHOD' ] );
?>